# -*- coding: utf-8 -*-
import hashlib
import time
import urllib
from collections import OrderedDict

import rsa
import base64
import requests
import json

from django.conf import settings

from async import async_job
from common.cache import redis_cache
from common.channel.pay import check_channel_order
from common.order import db as order_db
from common.order.model import PAY_STATUS
from common.utils import track_logging
from common.utils.exceptions import SignError, NotPayOrderError, ProcessedPayOrderError, NotResponsePayIdError
from common.utils.ip_address import check_valid_ip_address

_LOGGER = track_logging.getLogger(__name__)

APP_CONF = {
    '80000604': {
        'API_KEY': '8164892390F403E9E29AB9A27BA4483D',
        'RSA_PUB_KEY': 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ'
                       'KBgQCFc2BZJJgY90qKs4Xk5h3uVamGkNHCBhx'
                       'U7/apSi2ya3N01M2M9+d4nNcQOQUaOXpqx51YEM'
                       'aaH8cdGHkD5NwWVy8rMH0cD5q8YxEsePFhbnmR'
                       'cuY/7Vj3dtNBbKWA4DqePHmnmgpHaYC0HCln1fEmW'
                       'iaOsYjrVJal/SNEaMN+lwIDAQAB',
        'order_gateway': 'http://api.mdfmyd.top:8090/orderPay',  # 收银台服务地址
        'code_gateway': 'http://api.mdfmyd.top:8090/qrcodePay',  # 取码服务地址
        'query_gateway': 'http://api.mdfmyd.top:8090/order/query',  # 订单查询接口服务地址
    }
}


# 00000009=支付宝支付(支付宝扫码）alipay
# 00000010=支付宝WAP支付(支付宝H5)alipay_h5
# 00000012=银联快捷 unionpay
# 00000013=微信扫码支付 wechat

# 支付方式接口，service对应控制台上的支付方式
# 返回支付模式和渠道类型
def _get_pay_type(service):
    if service == 'alipay':
        return '00000009', '01'
    elif service == 'alipay_h5':
        return '00000010', '01'
    elif service == 'unionpay':
        return '00000012', '01'
    elif service == 'wechat':
        return '00000013', '01'
    elif service == 'alipay02':
        return '00000009', '02'
    elif service == 'alipay_h502':
        return '00000010', '02'
    elif service == 'unionpay02':
        return '00000012', '02'
    elif service == 'wechat02':
        return '00000013', '02'
    else:
        return '00000009', '01'


def _get_api_key(mch_id):
    return APP_CONF[mch_id]['API_KEY']


def _get_rsa_pub_key(mch_id):
    return APP_CONF[mch_id]['RSA_PUB_KEY']


def _get_order_gateway(mch_id):
    return APP_CONF[mch_id]['order_gateway']


def _get_code_gateway(mch_id):
    return APP_CONF[mch_id]['code_gateway']


def _get_query_gateway(mch_id):
    return APP_CONF[mch_id]['query_gateway']


def verify_notify_sign(params, key):
    sign = params['sign']
    params.pop('sign')
    response = OrderedDict((
        ('orderCode', params['orderCode']),
        ('totalAmount', params['totalAmount']),
        ('payMode', params['payMode']),
        ('orderStatus', params['orderStatus']),
        ('tradeNo', params['tradeNo']),
        ('payTime', params['payTime']),
    ))
    calculated_sign = _md5_sign(response, key)
    if sign != calculated_sign:
        _LOGGER.info("shanpupay sign: %s, calculated sign: %s", sign, calculated_sign)
        raise SignError('sign not pass, data: %s' % params)


# 签名sign，使用json格式，将交易报文中出现要求(M)的变量，按序号顺序排列，
# urlencode 在拼上md5key  然后在md5 base64
def _md5_sign(s, key):
    m = hashlib.md5()
    if s.get('body'):
        s.pop('body')
    if s.get('frontUrl'):
        s.pop('frontUrl')
    if s.get('extend'):
        s.pop('extend')
    st = urllib.quote(json.dumps(s, ensure_ascii=False).replace(' ', '')).replace('/', '%2F')
    m.update(st + key)
    sign = m.hexdigest()
    return base64.b64encode(sign)


def rsa_encrypt(biz_content, public_key):
    public_key = handle_pub_key(public_key)
    _p = rsa.PublicKey.load_pkcs1_openssl_pem(public_key)
    biz_content = biz_content.encode('utf-8')
    # 1024bit key
    default_encrypt_length = 117
    len_content = len(biz_content)
    if len_content < default_encrypt_length:
        return base64.b64encode(rsa.encrypt(biz_content, _p))
    offset = 0
    params_lst = []
    while len_content - offset > 0:
        if len_content - offset > default_encrypt_length:
            params_lst.append(rsa.encrypt(biz_content[offset:offset + default_encrypt_length], _p))
        else:
            params_lst.append(rsa.encrypt(biz_content[offset:], _p))
        offset += default_encrypt_length
    target = ''.join(params_lst)
    return base64.b64encode(target)


def handle_pub_key(key):
    """
    处理字符串型公钥
    公钥格式pem，处理成以-----BEGIN PUBLIC KEY-----开头，-----END PUBLIC KEY-----结尾的格式
    """
    start = '-----BEGIN PUBLIC KEY-----\n'
    end = '-----END PUBLIC KEY-----'
    result = ''
    # 分割key，每64位长度换一行
    divide = int(len(key) / 64)
    divide = divide if (divide > 0) else divide + 1
    line = divide if (len(key) % 64 == 0) else divide + 1
    for i in range(line):
        result += key[i * 64:(i + 1) * 64] + '\n'
    result = start + result + end
    return result


# 创建支付订单
def create_charge(pay, pay_amount, info):
    app_id = info['app_id']
    service = info.get('service')
    pay_mode, channel_type = _get_pay_type(service)
    extra = json.loads(info['extra']) if info.has_key('extra') else {}
    user_info = extra.get('user_info', {})
    now_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
    # 请求报文
    parameter_dict = OrderedDict((
        # 渠道类型 01-收银台  02-取码
        ('channelType', channel_type),
        # 商户订单号,商户唯一
        ('orderCode', str(pay.id)),
        # 订单金额以分为单位
        ('totalAmount', str(int(pay_amount * 100))),
        ('body', 'none'),
        # 支付模式
        ('payMode', pay_mode),
        # 客户端ip
        ('clientIp', user_info.get('device_ip') or '172.200.110.31'),
        # 异步通知地址
        ('notifyUrl', '{}/pay/api/{}/shanpupay/{}'.format(
            settings.NOTIFY_PREFIX, settings.NOTIFY_PATH, app_id)),
        ("frontUrl", "{}/pay/api/{}/shanpupay/".format(
            settings.NOTIFY_PREFIX, settings.RETURN_PATH)),
        # 银行编码
        ('bankCode', '01020000'),
        ('extend', 'none'),
        # 请求时间，使用时间戳，得到如：20190611170725
        ('reqTime', str(now_time)),
    ))
    _LOGGER.info('shanpupay create data:%s' % json.dumps(parameter_dict))
    # 秘文
    js_st = json.dumps(parameter_dict, ensure_ascii=False).replace(' ', '')
    ul = urllib.quote(js_st).replace('/', '%2F')
    rsa_base64 = rsa_encrypt(ul, _get_rsa_pub_key(app_id))
    # 签名
    sign = _md5_sign(parameter_dict, _get_api_key(app_id))
    # 通讯数据
    request_text = OrderedDict((
        ('charset', 'utf-8'),
        ('mid', app_id),
        ('data', rsa_base64),
        ('signType', 'MD5'),
        ('sign', sign),
    ))
    _LOGGER.info('shanpupay request text: %s', json.dumps(request_text))
    # 判断渠道类型
    channel_type = parameter_dict['channelType']
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    if channel_type == '01':
        response = requests.post(_get_order_gateway(app_id), data=request_text, headers=headers, timeout=5)
        cache_id = redis_cache.save_html(pay.id, response.text)
        _LOGGER.info('shanpupay response url: %s', settings.PAY_CACHE_URL + cache_id)
        return {'charge_info': settings.PAY_CACHE_URL + cache_id}
    elif channel_type == '02':
        response = requests.post(_get_code_gateway(app_id), data=request_text, headers=headers, timeout=5)
        data = json.loads(response.text)
        _LOGGER.info('shanpupay response text: %s', data)
        if data['respCode'] == '000000':
            return {'charge_info': data['qrcodeUrl']}
        return {'charge_info': data['respMsg']}
    else:
        _LOGGER.info('shanpupay channel_type error,should (01 or 02)')
        return {'charge_info': 'channel_type error,should (01 or 02)'}


# OK
def check_notify_sign(request, app_id):
    _LOGGER.info("shanpupay notify body: %s", request.body)
    api_key = _get_api_key(app_id)
    data = dict(request.POST.items())
    _LOGGER.info("shanpupay notify data: %s, order_id is: %s", data, data['orderCode'])
    verify_notify_sign(data, api_key)
    pay_id = data['orderCode']
    check_valid_ip_address(str(request.META['REMOTE_ADDR']), pay_id)
    if not pay_id:
        _LOGGER.error("shanpupay fatal error, out_trade_no not exists, data: %s", data)
        raise NotResponsePayIdError('shanpupay event does not contain pay ID')

    pay = order_db.get_pay(int(pay_id))
    if not pay:
        raise NotPayOrderError('shanpupay pay_id: %s invalid' % pay_id)
    if pay.status != PAY_STATUS.READY:
        raise ProcessedPayOrderError('shanpupay pay %s has been processed' % pay_id)

    mch_id = pay.mch_id  # 商户编号
    trade_status = data['orderStatus']
    trade_no = data['orderCode']
    total_fee = float(data['totalAmount']) / 100.0
    extend = {
        'trade_status': trade_status,
        'trade_no': trade_no,
        'total_fee': total_fee,
    }

    check_channel_order(pay_id, total_fee, app_id)
    if trade_status == '00':
        _LOGGER.info('shanpupay check order success, mch_id:%s pay_id:%s' % (mch_id, pay_id))
        order_db.add_pay_success(mch_id, pay_id, total_fee, trade_no, extend)
        # async notify
        async_job.notify_mch(pay_id)


def query_charge(pay_order, app_id):
    """ 查询订单 """
    pay_id = pay_order.id
    now_time = time.strftime('%Y%m%d%H%M%S', time.localtime())
    parameter_dict = OrderedDict((
        # 01：使用交易流水号查询 02：使用商户订单号查询
        ('orderCodeType', '02'),
        # 要查询订单的订单号
        ('orderCode', str(pay_id)),
        # 请求时间
        ('reqTime', str(now_time)),
    ))
    # 秘文
    js_st = json.dumps(parameter_dict, ensure_ascii=False).replace(' ', '')
    ul = urllib.quote(js_st).replace('/', '%2F')
    rsa_base64 = rsa_encrypt(ul, _get_rsa_pub_key(app_id))
    # 签名
    sign = _md5_sign(parameter_dict, _get_api_key(app_id))
    request_text = OrderedDict((
        ('charset', 'utf-8'),
        ('mid', app_id),
        ('data', rsa_base64),
        ('signType', 'MD5'),
        ('sign', sign),
    ))
    headers = {'Content-Type': 'application/x-www-form-urlencoded'}
    response = requests.post(_get_query_gateway(app_id), data=request_text, headers=headers, timeout=3)
    if response.status_code == 200:
        _LOGGER.info('shanpupay query rsp: %s', response.text)
        data = json.loads(response.text)
        trade_status = data['orderStatus']
        total_fee = float(data['totalAmount']) / 100.0
        trade_no = data['orderCode']
        extend = {
            'trade_status': trade_status,
            'trade_no': trade_no,
            'total_fee': total_fee,
        }
        check_channel_order(pay_id, total_fee, app_id)
        if trade_status == '00':
            _LOGGER.info('shanpupay query order success, mch_id:%s pay_id:%s' % (pay_order.mch_id, trade_no))
            res = order_db.add_pay_success(pay_order.mch_id, pay_order.id, total_fee, trade_no, extend)
            if res:
                async_job.notify_mch(pay_order.id)
        else:
            _LOGGER.info('shanpupay query order success, but not pay success, pay pay_id: %s,trade_status:%s'
                         % (trade_no, trade_status))
    else:
        _LOGGER.warn('shanpupay data error, status_code: %s', response.status_code)
